//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using ProjectBase.Core;
using ProjectBase.Core.Service;

using $prjsn$.Entity;
using $prjsn$.Common;
using $prjsn$.Common.Validation;
using $prjsn$.Common.Business;

namespace $safeprojectname$
{
    public class DynamicRulesReflector<TEntity, TFilter, TField> : IService
        where TEntity : BusinessObject
        where TFilter : BusinessObjectFilter, new()
        where TField : class, IField
    {
        public virtual IEnumerable<IValidationRule<TEntity>> BuildDynamicRules(IList<MappingDescription> mappingInfo, BusinessObjectTypes businessObjectType)
        {
            BusinessObjectType = businessObjectType;
            var fields = FieldFacade.GetAll();

            var rules = new List<IValidationRule<TEntity>>();

            // static, C# Properties
            foreach (var field in fields.Where(f => f.IsStatic))
            {
                BuildRulesForProperties(mappingInfo, field, rules);
            }

            var additionalReflector = Factory.CreateInstance<AdditionalRulesReflector<TEntity, TFilter, TField>>();

            // dynamic, IDictionary Additional
            foreach (var field in fields.Where(f => !f.IsStatic))
            {
                additionalReflector.BuildRulesForAdditional(mappingInfo, field, rules);
            }
            return rules;
        }

        #region Rules for Properties

        protected virtual void BuildRulesForProperties(IList<MappingDescription> mappingInfo, TField field, List<IValidationRule<TEntity>> rules)
        {
            MappingDescription mapping = mappingInfo
                .SingleOrDefault(m => m.PersistenceName.Equals(field.ColumnName)
                                      && m.CanUpdate);

            // Check uniqueness if required
            if (field.IsUnique)
            {
                var uniqueRule = new UniquePropertyRule<TEntity, int, TFilter>
                    (mapping.PropertyName
                    , field.IsRequired);

                rules.Add(uniqueRule);
            }

            var rule = CreatePropertyRule(field, mapping);

            if (rule.Is())
            {
                rules.Add(rule);
            }

            // TODO e.g. Create Address
            //TryCreateAddress(rules, field);
        }

        protected virtual IValidationRule<TEntity> CreatePropertyRule(TField field, MappingDescription mapping)
        {
            if (mapping.IsNull())
            {
                return null;
            }

            var property = mapping.PropertyInfo;
            IValidationRule<TEntity> rule;

            var canCreateRule = TryCreateStringRule(field, property, out rule)
                                || TryCreateValueTypeRule<int>(field, property, out rule)
                                || TryCreateValueTypeRule<decimal>(field, property, out rule)
                                || TryCreateValueTypeRule<DateTime>(field, property, out rule)
                                || TryCreateValueTypeRule<bool>(field, property, out rule);

            return canCreateRule ? rule : null;
        }

        protected virtual bool TryCreateStringRule(TField field, PropertyInfo property, out IValidationRule<TEntity> rule)
        {
            var isString = field.Type.IsEquivalentTo(typeof(string));

            if (!isString)
            {
                if (property.PropertyType.HasType<string>())
                {
                    throw new ArgumentException("Inconsistent settings. The {0} field has Additional PropertyName: '{1}', which has type mapped as string, while the IFiled is {2}".FormatWith(typeof(TEntity).Name, field.PropertyName, field.DataType));
                }
                rule = null;
                return false;
            }

            var newRule = new StringLengthRule<TEntity>(
                entity => property.GetValue(entity, null) as string
                , property.Name);

            // is Required
            if (field.IsRequired)
            {
                newRule.AsRequired();
            }

            // is there Lower Length
            int min;
            if (field.MinValue.IsNotEmpty()
                && field.MinValue.TryParse(out min))
            {
                newRule.RequireMinLength(min);
            }

            // is there Upper Length
            int max = int.MaxValue;
            if (field.MaxValue.IsNotEmpty() || field.Length > 0)
            {
                if (!field.MaxValue.TryParse(out max))
                {
                    max = field.Length.GetValueOrDefault(20000000);
                }
            }
            newRule.RequireMaxLength(max);

            rule = newRule;
            return true;
        }

        protected virtual bool TryCreateValueTypeRule<TValueType>(TField field, PropertyInfo property, out IValidationRule<TEntity> rule)
            where TValueType : struct, IComparable<TValueType>
        {
            var isValueType = field.Type.IsEquivalentTo(typeof(TValueType))
                || field.Type.IsEquivalentTo(typeof(TValueType?));

            if (!isValueType)
            {
                rule = null;
                return false;
            }

            var mappedType = property.PropertyType;
            isValueType = mappedType.IsEquivalentTo(typeof(TValueType))
                          || mappedType.IsEquivalentTo(typeof(TValueType?));

            if (!isValueType)
            {
                throw new ArgumentException("Inconsistent settings. The {0} field has Additional PropertyName: '{1}', which has type mapped as '{3}', while the IFiled is '{2}'".FormatWith(typeof(TEntity).Name, field.PropertyName, field.DataType, mappedType.Name));
            }

            var newRule = new ValueTypeNullableRule<TEntity, TValueType>(
                entity => property.GetValue(entity, null) as TValueType?
                , property.Name);

            // is Required
            if (field.IsRequired)
            {
                newRule.AsRequired();
            }

            // is there Lower Limit
            var min = default(TValueType);
            if (field.MinValue.IsEmpty()
                || !field.MinValue.TryParse(out min))
            {
                min = min.MinValue();
            }

            newRule.RequireMinValue(min);

            // is there Upper Limit
            var max = default(TValueType);
            if (field.MaxValue.IsEmpty()
                || !field.MaxValue.TryParse(out max))
            {
                max = max.MaxValue();
            }

            newRule.RequireMaxValue(max);

            rule = newRule;
            return true;
        }

        #endregion Rules for Properties

        #region properties

        protected virtual BusinessObjectTypes BusinessObjectType { get; set; }

        public virtual IFieldObjectFacade<TField> FieldFacade { protected get; set; }

        #endregion properties
    }
}